﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using QstkCore;
using QstkCore.DataFrame;
using QstkCore.Util;

namespace Tests
{
    [TestClass]
    public class DataFrameTests
    {
        private TimeSeriesDataFrame df;
        private TimeSeriesDataFrame df2;

        [TestInitialize]        
        public void TestSetup()
        {
            df = new TimeSeriesDataFrame(4, 3);
            df.ColumnNames = new List<string>{"APPL", "GOOG", "SPY"};
            df.TimeSeries = new List<DateTime> 
                { new DateTime(2013, 1, 1), new DateTime(2013, 1, 2), new DateTime(2013, 1, 3), new DateTime(2013, 1, 4) };
            df.SetRow(0, new[] { 0.1d, 0.5d, 0.9d });
            df.SetRow(1, new[] { 0.2d, 0.6d, 1.0d });
            df.SetRow(2, new[] { 0.3d, 0.7d, 1.1d });
            df.SetRow(3, new[] { 0.4d, 0.8d, 1.2d });

            df2 = new TimeSeriesDataFrame(4, 3);
            df2.ColumnNames = new List<string> { "DELL", "CSX", "VAR" };
            df2.TimeSeries = new List<DateTime> { new DateTime(2013, 5, 1), new DateTime(2013, 5, 2), new DateTime(2013, 5, 3), new DateTime(2013, 5, 4) };
            df2.SetRow(0, new[] { 1.0d, 5.0d, 9.0d });
            df2.SetRow(1, new[] { 2.0d, 6.0d, 10.0d });
            df2.SetRow(2, new[] { 3.0d, 7.0d, 11.0d });
            df2.SetRow(3, new[] { 4.0d, 8.0d, 12.0d });

        }

        [TestMethod]
        public void HeadTest()
        {
            Assert.AreEqual(4, df.Nrow);
            Assert.AreEqual(3, df.Ncol);

            var expectedHead = new[] {0.1d, 0.5d, 0.9d};
            CollectionAssert.AreEqual(expectedHead, actual: df.Head(1).ToList());
        }

        [TestMethod]
        public void TailTest()
        {
            var expectedTail = new[] { 0.4d, 0.8d, 1.2d };
            CollectionAssert.AreEqual(expectedTail, actual: df.Tail(1).ToList());
        }

        [TestMethod]
        public void ColumnNameIndexing()
        {
            var result = df["APPL"];
            var expected = new[] { 0.1d, 0.2d, 0.3d, 0.4d };
            CollectionAssert.AreEqual(expected, actual: result.ToList());
        }

        [TestMethod]
        public void DateTimeIndexing()
        {
            var result = df[new DateTime(2013, 1, 3)];
            var expected = new[] { 0.3d, 0.7d, 1.1d };
            CollectionAssert.AreEqual(expected, actual: result.ToList());
        }

        [TestMethod]
        public void AdditionDataFrames()
        {
            var result = df + df2;
            var expected = new[]
                               {
                                   1.1d, 5.5d, 9.9d,
                                   2.2d, 6.6d, 11.0d,
                                   3.3d, 7.7d, 12.1d,
                                   4.4d, 8.8d, 13.2d
                               };
            CollectionAssert.AreEqual(expected, actual: result.ToList());
        }

        [TestMethod]
        public void SubractionDataFrames()
        {
            var result = df - df2;
            var expected = new[]
                               {
                                    -0.9,	-4.5,	-8.1,
                                    -1.8,	-5.4,	-9,
                                    -2.7,	-6.3,	-9.9,
                                    -3.6,	-7.2,	-10.8,
                               };
            CollectionAssert.AreEqual(expected, actual: result.ToList());
        }

        [TestMethod]
        public void MultiplicationDataFrames()
        {
            var result = df * df2;
            var expected = new[]
                               {
                                    0.1, 2.5, 8.1,
                                    0.4, 3.6, 10.0,
                                    0.9, 4.9, 12.1,
                                    1.6, 6.4, 14.4
                               };

            Assert.IsTrue(expected.Zip(result, (a, b) => AlmostEqual(a, b, 0.0001)).All(o => object.Equals(o, true) ));
        }

        [TestMethod]
        public void DivisionDataFrames()
        {
            var result = df / df2;
            var expected = new[]
                               {
                                    0.1,	0.1,	0.1,
                                    0.1,	0.1,	0.1,
                                    0.1,	0.1,	0.1,
                                    0.1,	0.1,	0.1,
                               };

            Assert.IsTrue(expected.Zip(result, (a, b) => AlmostEqual(a, b, 0.0001)).All(o => object.Equals(o, true)));
        }

        public static bool AlmostEqual(double a, double b, double epsilon)
        {
            double absA = Math.Abs(a);
            double absB = Math.Abs(b);
            double diff = Math.Abs(a - b);

            if (a * b == 0) { // a or b or both are zero
                // relative error is not meaningful here
                return diff < (epsilon * epsilon);
            } else { // use relative error
                return diff / (absA + absB) < epsilon;
            }
        }

        [TestMethod]
        public void FillForwardTest()
        {
            var ldf = new TimeSeriesDataFrame(4, 3);
            ldf.ColumnNames = new List<string> { "APPL", "GOOG", "SPY" };
            ldf.TimeSeries = new List<DateTime> { new DateTime(2013, 1, 1), new DateTime(2013, 1, 2), new DateTime(2013, 1, 3), new DateTime(2013, 1, 4) };
            ldf.SetRow(0, new[] { 0.1d, double.NaN, 0.9d });
            ldf.SetRow(1, new[] { double.NaN, double.NaN, 1.0d });
            ldf.SetRow(2, new[] { double.NaN, 0.7d, double.NaN });
            ldf.SetRow(3, new[] { 0.4d, 0.8d, double.NaN });

            ldf.ReplaceNanFill(FillDirection.Forward);

            var expected = new[]
                               {
                                    0.1,	double.NaN,	0.9,
                                    0.1,	double.NaN,	1.0,
                                    0.1,	0.7,	    1.0,
                                    0.4,	0.8,	    1.0,
                               };

            CollectionAssert.AreEqual(expected, ldf.ToList());
        }
    }
}
